#!/usr/bin/python
# -*- coding: utf8 -*-
import json
import os
import shutil
import platform
import re
import sys
import time
import threading
import esky
from PyQt4 import QtCore
from copy import deepcopy
from traceback import format_exc

from PyQt4.QtCore import Qt, QTimer, pyqtSignal
from PyQt4.QtGui import QApplication, QStandardItemModel, QStandardItem, QInputDialog

from odbc_p.DB import loader
from util.crypter import DataCrypter
from util.logger import process_log
from util.dshelper import DS
from util.client_service import ClientService
from util.tools import byte_format
from util.app_global import version, version_suffix, conf_version_require, changelog_template, g
from widget.layout import Ui_OpenDS
from widget.progress import Ui_Progress
from widget.handler.sync_log import LogHandler
from widget.handler.sync_origin import OriginHandler
from widget.handler.sync_file import FileHandler
from OpenDSClient import sync
from widget.tools import render_database_tree, create_field_list_model, clicked_item, set_table_state, \
    render_sync_tree, MessageBox, TabMap, copy_and_overwrite
from widget.configure_template import config, user_info, db, append_table


class Sync(QtCore.QThread):
    dispatcher = pyqtSignal(int, object)

    def __init__(self, parent):
        QtCore.QThread.__init__(self, parent)
        self.sync_thread = None

    def run(self):
        while True:
            try:
                message = g.get_signal()
                self.dispatcher.emit(message[0], message[1])
            except Exception:
                pass

    def sync_start(self):
        self.sync_thread = threading.Thread(target=sync)
        self.sync_thread.setDaemon(True)
        self.sync_thread.start()

    def sync_done(self):
        if self.sync_thread:
            pass


class App(Ui_OpenDS):
    _tab = 0
    _need_to_backup = ['conf', 'where', 'check']
    _config = config
    _user_info = user_info
    _db = db

    _ds_name_map = {}

    _append_table = append_table

    _connect_info = {}

    _config_history = {}

    _cond_set = False

    _data = {}

    _tree_model = {}

    _sync_tree_model = None

    _tree_view = {}
    _server = {}
    _port = {}
    _type = {}
    _uid = {}
    _pwd = {}

    _table_model_origin = {}
    _current_select_table_model = None
    _current_select_table_data = {}
    _time = 0
    _logged = False
    _first_install = False

    ds = None
    dbs = None
    current_tb_id = None

    def __init__(self):
        Ui_OpenDS.__init__(self)

        self.setAttribute(QtCore.Qt.WA_DeleteOnClose)

        self.progress = Ui_Progress()
        self.box = MessageBox(self)

        self.configure_ui()

        self._syncing = Sync(self)
        self._syncing.dispatcher.connect(self.message_loop)
        self._syncing.start()

        self.loghandler = LogHandler(self)
        self.filehandler = FileHandler(self)
        self.originhandler = OriginHandler(self)

        self._tab_map = ['origin', 'md5_diff', 'local_binlogs', 'aliyun_binlogs', 'log_sync']
        self._connect_map = ['server', 'port', 'type', 'uid', 'pwd']

        self.configure_model()

        self.create_widget_map()

        self.get_install_status()
        self.check_conf_version()
        try:
            with open('conf/opends.conf') as conf:
                self._config_history = json.load(conf)
                self.setup_config()
        except Exception:
            print format_exc()
        self.app = None
        self.best_version = None
        self.check_upgrade()
        if self.best_version:
            self.pushButton_upgrade.setText(u'有新版本')

    def configure_ui(self):
        self.hidden_unused()
        self.progress.pushButton.setText(u'更新')
        self.progress.pushButton.connect(self.progress.pushButton, QtCore.SIGNAL('clicked()'), self.progress_hook)

        self.label_version.setText(u"当前版本: %s%s" % (version, version_suffix))
        self.comboBox_db_type_aliyun.addItems(['MYSQL'])
        self.comboBox_db_type_local.addItems(['MYSQL'])
        self.comboBox_db_type_md5.addItems(['MYSQL'])
        self.comboBox_db_type_origin.addItems(['ODBC', 'MYSQL', 'MSSQL', 'ORACLE', 'ACCESS', 'FIREBIRD'])
        self._tab = TabMap.ORIGIN

        self.block_combobox_signal()
        self.comboBox_cond_origin.addItems(['>', '<', '>=', '<=', '<>', '=', 'like'])
        self.comboBox_cond_origin.setCurrentIndex(-1)
        self.comboBox_value_origin.addItems(['$VALUE$'])
        self.comboBox_value_origin.setCurrentIndex(-1)
        self.comboBox_format_origin.addItems(['%Y-%m-%d %H:%M:%S', '%Y-%m-%d'])
        self.comboBox_format_origin.setCurrentIndex(-1)
        self.comboBox_fetch_rows.addItems(['2000', '5000', '10000', '20000'])
        self.comboBox_fetch_rows.setCurrentIndex(-1)
        self.release_combobox_signal()

    def configure_model(self):
        for model in self._tab_map:
            self._tree_model[model] = QStandardItemModel()
            self._data[model] = None
        self.treeView_origin.setModel(self._tree_model[self._tab_map[TabMap.ORIGIN]])
        self.treeView_md5.setModel(self._tree_model[self._tab_map[TabMap.MD5]])
        self.treeView_aliyun.setModel(self._tree_model[self._tab_map[TabMap.ALIYUN]])
        self.treeView_local.setModel(self._tree_model[self._tab_map[TabMap.LOCAL]])

        self._sync_tree_model = QStandardItemModel()
        self.treeView_sync.setModel(self._sync_tree_model)

    def create_widget_map(self):
        self._tree_view = {
            self._tab_map[TabMap.ORIGIN]: self.treeView_origin,
            self._tab_map[TabMap.MD5]: self.treeView_md5,
            self._tab_map[TabMap.LOCAL]: self.treeView_local,
            self._tab_map[TabMap.ALIYUN]: self.treeView_aliyun
        }

        self._server = {
            self._tab_map[TabMap.ORIGIN]: self.lineEdit_db_addr_origin,
            self._tab_map[TabMap.MD5]: self.lineEdit_db_addr_md5,
            self._tab_map[TabMap.LOCAL]: self.lineEdit_db_addr_local,
            self._tab_map[TabMap.ALIYUN]: self.lineEdit_db_addr_aliyun
        }

        self._port = {
            self._tab_map[TabMap.ORIGIN]: self.lineEdit_db_port_origin,
            self._tab_map[TabMap.MD5]: self.lineEdit_db_port_md5,
            self._tab_map[TabMap.LOCAL]: self.lineEdit_db_port_local,
            self._tab_map[TabMap.ALIYUN]: self.lineEdit_db_port_aliyun
        }

        self._type = {
            self._tab_map[TabMap.ORIGIN]: self.comboBox_db_type_origin,
            self._tab_map[TabMap.MD5]: self.comboBox_db_type_md5,
            self._tab_map[TabMap.LOCAL]: self.comboBox_db_type_local,
            self._tab_map[TabMap.ALIYUN]: self.comboBox_db_type_aliyun
        }

        self._uid = {
            self._tab_map[TabMap.ORIGIN]: self.lineEdit_db_username_origin,
            self._tab_map[TabMap.MD5]: self.lineEdit_db_username_md5,
            self._tab_map[TabMap.LOCAL]: self.lineEdit_db_username_local,
            self._tab_map[TabMap.ALIYUN]: self.lineEdit_db_username_aliyun
        }

        self._pwd = {
            self._tab_map[TabMap.ORIGIN]: self.lineEdit_db_password_origin,
            self._tab_map[TabMap.MD5]: self.lineEdit_db_password_md5,
            self._tab_map[TabMap.LOCAL]: self.lineEdit_db_password_local,
            self._tab_map[TabMap.ALIYUN]: self.lineEdit_db_password_aliyun
        }

    def get_install_status(self):
        if platform.system() == 'Windows':
            pwd = os.getcwd()
            paths = pwd.split('\\')
            if paths[-2] != 'appdata':
                self._first_install = True
        else:
            pass

    def hidden_unused(self):
        self.pushButton_run.setDisabled(True)
        self.pushButton_create_task.setDisabled(True)

        self.pushButton_upgrade.setHidden(False)

        self.tabWidget_connect_tab.removeTab(4)
        self.tabWidget_connect_tab.removeTab(4)

    def check_upgrade(self):
        if getattr(sys, "frozen", False):
            executable = sys.executable
            if platform.system() == 'Windows':
                executable = "%s\UI.exe" % os.getcwd()
            try:
                process_log().info(ClientService.upgrade_url)
                self.app = esky.Esky(executable, ClientService.upgrade_url)
                self.best_version = self.app.find_update()
                return True
            except Exception:
                # MessageBox().critical(self, u'错误', u'网络错误.')
                process_log().error(format_exc())
                return False
        else:
            # self.progress.show()
            pass
        return False

    def setup_config(self):
        """
        Args:
            self
        Returns:
            None
        Examples:
            None
        """
        if not self._config_history:
            return
        user_info = self._config_history.get('user_info', {})
        self._user_info = user_info
        self.lineEdit_bdp_username.setText(user_info.get('username', ''))
        pwd = user_info.get('password', '')
        try:
            pwd = DataCrypter().decrypt_data(pwd)
        except Exception:
            pass
        self.lineEdit_bdp_password.setText(pwd)
        self.lineEdit_bdp_domain.setText(user_info.get('domain', ''))
        for db in self._config_history.get('db', []):
            if db.get('type') in ['CSV', 'GEA']:
                self.filehandler.recover_ui(db)
                continue
            self._server[db['db_increase_type']].setText(db['server'])
            self._port[db['db_increase_type']].setText(db['port'])
            index = self._type[db['db_increase_type']].findText(db['type'])
            self._type[db['db_increase_type']].setCurrentIndex(index)
            self._uid[db['db_increase_type']].setText(db['uid'])
            self._pwd[db['db_increase_type']].setText(db['pwd'])

            db_binlog = db.get('db_binlog', {})

            if db.get('db_increase_type') == 'local_binlogs':
                self.lineEdit_binlog_path_local.setText(db_binlog.get('path', ''))
            elif db.get('increase_type') == 'aliyun_binlogs':
                self.lineEdit_instance_id_aliyun.setText(db_binlog.get('instance', ''))
                self.lineEdit_key_id_aliyun.setText(db_binlog.get('id', ''))
                self.lineEdit_key_secret_aliyun.setText(db_binlog.get('secret', ''))

    def database_connect(self):
        """
        connecting database and read the schema to render treeview
        """

        def config_filter(data, connect_info):
            # get the db first
            for db in self._config_history.get('db', []):
                if db.get('server') == connect_info.get('server') \
                        and db.get('port') == connect_info.get('port') \
                        and db.get('uid') == connect_info.get('uid') \
                        and db.get('pwd') == connect_info.get('pwd') \
                        and db.get('type') == connect_info.get('type') \
                        and db.get('db_increase_type') == self._tab_map[self._tab]:
                    db_name = db.get('database')
                    self._ds_name_map[db_name] = db.get('ds_name')
                    tables = db.get('append_table', [])
                    for table in tables:
                        try:
                            tb = data.get(db_name).get(table.get('name'))
                            tb['selected'] = 1
                            if self._tab != TabMap.ORIGIN:
                                continue
                            fields = table.get('select', [])
                            for fd in tb['fields']:
                                if fd['name'] in fields:
                                    fd['selected'] = 1
                            index_field = table.get('index_field', {}).get('name')
                            tb['increase_field'] = index_field
                            where = table.get('where', '')
                            if where:
                                array = where.split(' ')
                                cond = array[2]
                                value = ' '.join(array[3:])
                                res = re.match(r".*\('(?P<value>\S+)',.*", value)
                                if res is not None:
                                    value = res.group('value')
                                field = array[1]
                                tb['increase_field'] = field
                                tb['increased'] = {
                                    'cond': cond,
                                    'value': value.replace("'", '')
                                }
                            tb['fetch_rows'] = table.get('fetch_rows', '10000')
                            text_date = table.get('text_date', {})
                            tb['text_date']['format'] = text_date.get('format') if text_date.get('format') else ''
                            tb['text_date']['value'] = text_date.get('value') if text_date.get('value') else ''
                            if not index_field:
                                tb['increase_field'] = text_date.get('field') if text_date.get('field') else ''
                        except Exception:
                            pass
                    self.pushButton_run.setEnabled(True)
                    self.pushButton_save.setEnabled(True)
                    self.pushButton_create_task.setEnabled(True)
            return data

        def connect(connect_info):
            """
            connect database and read the schema to self.data if success

            Args:
                connect_info: include the connect to Sql database's args
            """
            try:
                connection = loader(connect_info)
                data = connection.get_all_db_info(view=True if self._tab == TabMap.ORIGIN else False)
                MessageBox().information(self, u'信息', u'连接成功', MessageBox.Yes)
                if self._logged:
                    self.pushButton_save.setDisabled(False)
                return config_filter(data, connect_info) if self._config_history.get('db', None) else data
            except Exception, e:
                process_log().error(u'%s' % format_exc())
                MessageBox().critical(self, u'错误', u'%s' % str(e).decode('unicode_escape'))
                return {}

        _connect_info = {
            'server': str(self._server[self._tab_map[self._tab]].text()),
            'port': str(self._port[self._tab_map[self._tab]].text()),
            'type': str(self._type[self._tab_map[self._tab]].currentText()),
            'uid': str(self._uid[self._tab_map[self._tab]].text()),
            'pwd': str(self._pwd[self._tab_map[self._tab]].text()),
        }
        if _connect_info['type'] == 'ORACLE':
            os.environ['NLS_LANG'] = 'SIMPLIFIED CHINESE_CHINA.UTF8'
            database, ok = QInputDialog().getText(self, u'提示', u'请输服务名称或SID')
            if ok:
                _connect_info['database'] = str(database)
            else:
                return
        elif _connect_info['type'] == 'ACCESS':
            db_file = _connect_info['server']
            if not os.path.exists(db_file):
                MessageBox().critical(self, u'提示', u'数据库%s不存在.' % db_file)
                return
            if not os.path.isdir('tmp'):
                os.mkdir('tmp')
            try:
                shutil.copyfile(db_file, 'tmp/%s' % os.path.basename(db_file))
                _connect_info['database'] = os.path.abspath('tmp/%s' % os.path.basename(db_file))
            except Exception, e:
                raise e
        self._data[self._tab_map[self._tab]] = _data = connect(_connect_info)
        if _data:
            self._connect_info[self._tab_map[self._tab]] = _connect_info
            _header = QStandardItem()
            _header.setText('%s@%s' % (_connect_info['uid'], _connect_info['server']))
            self._tree_model[self._tab_map[self._tab]].setHorizontalHeaderItem(0, _header)
            _tree_view = self._tree_view[self._tab_map[self._tab]]
            self.tableView_origin.setModel(None)
            self.clear_cond()
            render_database_tree(_tree_view.model(), _data)

    def opends_login(self):
        domain = str(self.lineEdit_bdp_domain.text())
        username = str(self.lineEdit_bdp_username.text())
        password = str(self.lineEdit_bdp_password.text())
        if not (domain and username and password):
            MessageBox().critical(self, u'提示', u'请输入完整的企业域,用户名,密码.')
            return
        try:
            password = DataCrypter().encrypt_data(password)
            self.ds = DS(domain, username, password)
        except Exception, e:
            MessageBox().critical(self, u'错误', str(e), MessageBox.Yes)
            return
        MessageBox().information(self, u'成功', u'登录成功!', MessageBox.Yes)
        self._user_info['domain'] = domain
        self._user_info['username'] = username
        self._user_info['password'] = password
        self._logged = True
        self._config['user_info'] = self._user_info

    def tab_select(self, index):
        self._tab = index
        if index == 0:
            pass

    def block_combobox_signal(self):
        self.comboBox_field_origin.blockSignals(True)
        self.comboBox_cond_origin.blockSignals(True)
        self.comboBox_value_origin.blockSignals(True)
        self.comboBox_format_origin.blockSignals(True)
        self.comboBox_diff_origin.blockSignals(True)
        self.comboBox_fetch_rows.blockSignals(True)

    def release_combobox_signal(self):
        self.comboBox_field_origin.blockSignals(False)
        self.comboBox_cond_origin.blockSignals(False)
        self.comboBox_value_origin.blockSignals(False)
        self.comboBox_format_origin.blockSignals(False)
        self.comboBox_diff_origin.blockSignals(False)
        self.comboBox_fetch_rows.blockSignals(False)

    def set_field_check_state(self, field_list_model, state):
        """
        set fields' check state to check all or not, depend on table's state
        Args:
            field_list_model: the model of field list
            state: the state of table, True if table is checked else False
        Returns:
            None
        """
        if not field_list_model:
            return
        check_flag = Qt.Checked if state else Qt.Unchecked

        field_models = [field_list_model.item(index) for index in xrange(0, field_list_model.rowCount())]
        selected_count = sum([1 for field in field_models if field.checkState() == Qt.Checked])

        for fd_index in xrange(0, field_list_model.rowCount()):
            fd_model = field_list_model.item(fd_index)
            fd_model.setEnabled(state)
            field_list_model.item(fd_index, 1).setEnabled(state)
            if selected_count == 0 or selected_count == len(field_models):
                fd_model.setCheckState(check_flag)
            self.comboBox_field_origin.addItem(fd_model.text())
        self.comboBox_field_origin.setCurrentIndex(-1)

    def set_check_state(self, table_model=None, field_list_model=None, database_model=None):
        """
        set check state for click position.
        Args:
            table_model:
            field_list_model:
            database_model:
        """
        if table_model:
            state = True if table_model.checkState() == Qt.Checked else False
            self.set_field_check_state(field_list_model, state)
            selected_tb_count = 0
            for table_index in xrange(0, database_model.rowCount()):
                if database_model.child(table_index).checkState() == Qt.Checked:
                    selected_tb_count += 1
            if selected_tb_count == 0:
                database_model.setCheckState(Qt.Unchecked)
            elif selected_tb_count == database_model.rowCount():
                database_model.setCheckState(Qt.Checked)
            else:
                database_model.setCheckState(Qt.PartiallyChecked)
        else:
            state = database_model.checkState()
            db_name = str(database_model.text())
            for index in xrange(0, database_model.rowCount()):
                table_model = database_model.child(index)
                table_model.setCheckState(state)

                tb_name = u'%s' % str(table_model.text())
                tb_data = self._data[self._tab_map[self._tab]][db_name][tb_name]
                fd_list_model = tb_data.get('model', None)
                if not fd_list_model:
                    fd_list_model = create_field_list_model(tb_data.get('fields'))
                    self._data[self._tab_map[self._tab]][db_name][tb_name]['model'] = fd_list_model
                self.set_field_check_state(fd_list_model, True if state == Qt.Checked else False)

    def set_condition_value(self, table):
        fetch_rows = table.get('fetch_rows', "10000")
        self.comboBox_fetch_rows.setEditText(fetch_rows)

        increase_field = table.get('increase_field')
        self.comboBox_field_origin.setCurrentIndex(self.comboBox_field_origin.findText(increase_field)
                                                   if increase_field else -1)

        increase_cond = table.get('increased', {}).get('cond')
        self.comboBox_cond_origin.setCurrentIndex(self.comboBox_cond_origin.findText(increase_cond)
                                                  if increase_cond else -1)

        increase_value = table.get('increased', {}).get('value')
        self.comboBox_value_origin.setEditText(increase_value if increase_value else '')
        self.comboBox_format_origin.setDisabled(True if increase_value else False)
        self.comboBox_diff_origin.setDisabled(True if increase_value else False)

        text_date_format = table.get('text_date', {}).get('format')
        self.comboBox_format_origin.setEditText(text_date_format if text_date_format else '')

        text_date_value = table.get('text_date', {}).get('value')
        self.comboBox_diff_origin.setEditText(text_date_value if text_date_value else '')
        self.comboBox_cond_origin.setDisabled(True if text_date_value else False)
        self.comboBox_value_origin.setDisabled(True if text_date_value else False)

    @staticmethod
    def clicked_item(index):
        """
            Find the database and table that user clicked
        Args:
            index: QStanderItemIndex
        Returns:
            (
                db_item: QStandardItem,
                tb_item: QStandardItem
            )
        """
        item = index.model().itemFromIndex(index)

        parent = item.parent()

        if parent:
            db_item = parent
            tb_item = item
        else:
            db_item = item
            tb_item = None

        return db_item, tb_item

    def tree_click(self, index):
        db_item, tb_item = clicked_item(index)

        self.set_check_state(tb_item, None, db_item)

    def table_origin_click(self, index):
        """
            process user's click on table
        Args:
            index: the index of the clicked item

        Returns:
            None:
        """
        db_item, tb_item = clicked_item(index)

        database_name = str(db_item.text())
        table_name = tb_item.text() if tb_item else None

        self.block_combobox_signal()

        # click a table name
        if table_name:

            self.comboBox_field_origin.clear()
            table_item = index.model().itemFromIndex(index)
            table_name, database_name = str(table_name), str(database_name)
            table_name = u'%s' % table_name
            _table = self._data[self._tab_map[self._tab]][database_name][table_name]
            _fields = _table.get('fields', [])
            field_list_model = _table.get('model', None)
            if not field_list_model:
                field_list_model = create_field_list_model(_fields)
                self._data[self._tab_map[self._tab]][database_name][table_name]['model'] = field_list_model

            self.set_check_state(table_item, field_list_model, db_item)

            self.set_condition_value(_table)

            self._current_select_table_model = field_list_model
            self._current_select_table_data = _table
            self.tableView_origin.setModel(field_list_model)
            self.tableView_origin.verticalHeader().setVisible(False)

        # click a database name
        else:
            db_item = index.model().itemFromIndex(index)
            if db_item.checkState() != Qt.PartiallyChecked:
                # check or not unchecked all tables
                for row in xrange(0, db_item.rowCount()):
                    tb_item = db_item.child(row)
                    tb_name = u'%s' % str(tb_item.text())
                    table_data = self._data[self._tab_map[self._tab]][database_name][tb_name]
                    field_list_model = table_data.get('model')
                    if not field_list_model:
                        field_list_model = create_field_list_model(table_data.get('fields'))
                        table_data['model'] = field_list_model
                self.set_check_state(None, None, db_item)
            else:
                pass
            self.tableView_origin.setModel(None)

        self.release_combobox_signal()

    def field_origin_change(self, text):
        """
            Handle the text change of origin increase's field combobox
        Args:
            text: the current text of field combobox
        """
        self._current_select_table_data['increase_field'] = str(text)

    def cond_origin_change(self, text):
        self._current_select_table_data['increased']['cond'] = str(text)

    def value_origin_change(self, text):
        self.comboBox_diff_origin.setDisabled(True if len(text) != 0 else False)
        self.comboBox_format_origin.setDisabled(True if len(text) != 0 else False)
        self._current_select_table_data['increased']['value'] = str(text)

    def format_origin_change(self, text):
        self._current_select_table_data['text_date']['format'] = str(text)

    def diff_origin_change(self, text):
        self.comboBox_value_origin.setDisabled(True if len(text) != 0 else False)
        self.comboBox_cond_origin.setDisabled(True if len(text) != 0 else False)
        self._current_select_table_data['text_date']['value'] = str(text)

    def fetch_rows_origin_change(self, text):
        self._current_select_table_data['fetch_rows'] = str(text)

    def clear_cond(self):

        """
            Clear all the widget content of origin increase tab
        """
        self.block_combobox_signal()

        self.comboBox_field_origin.clear()
        self.comboBox_cond_origin.setCurrentIndex(-1)
        self.comboBox_value_origin.setCurrentIndex(-1)
        self.comboBox_format_origin.setCurrentIndex(-1)
        self.comboBox_diff_origin.clear()

        self.release_combobox_signal()

    def select_log_file(self):
        self.loghandler.file_select()

    def add_field_rule(self):
        self.loghandler.add_rule()

    def rule_change(self, rule):
        self.loghandler.rule_change(rule)

    def log_table_cell_changed(self, row, col):
        self.loghandler.table_cell_changed(row, col)

    def select_file(self):
        self.filehandler.file_select()

    def file_type_changed(self, index):
        self.filehandler.file_type_changed(index)

    def file_preview_cell_changed(self, row, col):
        self.filehandler.table_cell_changed(row, col)

    def save(self):
        if not (self._user_info['username'] and self._user_info['password'] and self._user_info['domain']):
            MessageBox().critical(self, u'提示', u'请先登陆')
            return
        self._config['user_info'] = self._user_info

        def gen_db_info(db_name, increase_type):
            db = deepcopy(self._db)
            db['database'] = db_name
            db['server'] = self._connect_info[self._tab_map[increase_type]]['server']
            db['port'] = self._connect_info[self._tab_map[increase_type]]['port']
            db['uid'] = self._connect_info[self._tab_map[increase_type]]['uid']
            db['pwd'] = self._connect_info[self._tab_map[increase_type]]['pwd']
            db['type'] = self._connect_info[self._tab_map[increase_type]]['type']
            db['db_increase_type'] = self._tab_map[increase_type]
            if os.path.exists(db_name):
                db['ds_name'] = os.path.basename(db_name)
            return db

        def gen_origin():
            """
            read the _data and return the list of db config structure.
            :rtype list
            """
            db_items = self._data[self._tab_map[TabMap.ORIGIN]].items()
            tree_model = self._tree_model[self._tab_map[TabMap.ORIGIN]]
            for db_name, tables in db_items:

                # continue if database which in tree_model is unchecked
                db_model = tree_model.findItems(db_name)[0]
                if db_model.checkState() == Qt.Unchecked:
                    continue

                db = gen_db_info(db_name, TabMap.ORIGIN)

                if not db['ds_name']:
                    db['ds_name'] = self._ds_name_map.get(db_name, db_name)

                del db['db_binlog']

                selected_db = False
                for tb_name, table in tables.items():
                    # continue if table which in tree_model is unchecked
                    for tb_index in xrange(0, db_model.rowCount()):
                        if str(db_model.child(tb_index).text()) == tb_name:
                            tb_model = db_model.child(tb_index)
                    if tb_model and tb_model.checkState() == Qt.Unchecked:
                        continue
                    model = table.get('model')
                    if model:
                        tb = deepcopy(self._append_table)
                        tb['name'] = tb_name
                        selected_tb = False
                        increase_field = str(table.get('increase_field'))
                        for fd_index in xrange(0, model.rowCount()):
                            if model.item(fd_index).checkState() == Qt.Checked:
                                field_name = str(model.item(fd_index).text())
                                tb['select'].append(field_name)
                                selected_tb = selected_db = True
                        if selected_tb:
                            diff = str(table.get('text_date', {}).get('value'))
                            fmt = str(table.get('text_date', {}).get('format'))
                            if diff == '' or fmt == '' or increase_field == '':
                                del tb['text_date']
                            else:
                                self._cond_set = True
                                tb['text_date'] = {
                                    'field': str(table.get('increase_field')),
                                    'format': fmt,
                                    'value': diff
                                }

                            value = str(table.get('increased', {}).get('value'))
                            cond = str(table.get('increased', {}).get('cond'))

                            if value == '' or cond == '' or increase_field == '':
                                del tb['where'], tb['index_field']
                            else:
                                self._cond_set = True
                                tb['index_field'] = {
                                    'name': str(table.get('increase_field'))
                                }
                                tb['where'] = "where %s %s '%s'" % \
                                              (table.get('increase_field'), cond, value)
                                if db['type'] == 'ORACLE':
                                    for field in table.get('fields'):
                                        if field.get('name') == increase_field:
                                            if field.get('type') == 'date':
                                                tb['where'] = "where %s %s \
                                                                to_date('%s', 'YYYY-MM-DD HH24:MI:SS')" % \
                                                    (table.get('increase_field'), cond, value)

                            tb['fetch_rows'] = str(table.get('fetch_rows', '10000'))

                            db['append_table'].append(tb)
                if selected_db:
                    db['append_table'] = sorted(db['append_table'], key=lambda x: x.get('name'))
                    self._config['db'].append(db)
            return True

        def gen_md5():
            model = self._tree_model[self._tab_map[TabMap.MD5]]
            for db_index in xrange(0, model.rowCount()):
                db_model = model.item(db_index)
                selected_db = False
                db_name = str(db_model.text())
                db = gen_db_info(db_name, TabMap.MD5)
                for tb_index in xrange(0, db_model.rowCount()):
                    tb_model = db_model.child(tb_index)
                    if tb_model.checkState() == Qt.Checked:
                        tb = deepcopy(self._append_table)
                        tb['name'] = str(tb_model.text())
                        del tb['select'], tb['index_field'], tb['where'], tb['text_date']
                        db['append_table'].append(tb)
                        selected_db = True
                if selected_db:
                    self._config['db'].append(db)
            return True

        def gen_local():
            model = self._tree_model[self._tab_map[TabMap.LOCAL]]
            for db_index in xrange(0, model.rowCount()):
                db_model = model.item(db_index)
                selected_db = False
                db_name = str(db_model.text())
                db = gen_db_info(db_name, TabMap.LOCAL)

                db['db_binlog']['path'] = str(self.lineEdit_binlog_path_local.text())

                for tb_index in xrange(0, db_model.rowCount()):
                    tb_model = db_model.child(tb_index)
                    if tb_model.checkState() == Qt.Checked:
                        tb = deepcopy(self._append_table)
                        tb['name'] = str(tb_model.text())
                        del tb['select'], tb['index_field'], tb['where'], tb['text_date']
                        db['append_table'].append(tb)
                        selected_db = True
                if selected_db:
                    self._config['db'].append(db)
            return True

        def gen_aliyun():
            model = self._tree_model[self._tab_map[TabMap.ALIYUN]]
            for db_index in xrange(0, model.rowCount()):
                db_model = model.item(db_index)
                selected_db = False
                db_name = str(db_model.text())
                db = gen_db_info(db_name, TabMap.ALIYUN)

                db['db_binlog'] = {
                    'instance': str(self.lineEdit_instance_id_aliyun.text()),
                    'secret': str(self.lineEdit_key_secret_aliyun.text()),
                    'id': str(self.lineEdit_key_id_aliyun.text())
                }

                for tb_index in xrange(0, db_model.rowCount()):
                    tb_model = db_model.child(tb_index)
                    if tb_model.checkState() == Qt.Checked:
                        tb = deepcopy(self._append_table)
                        tb['name'] = str(tb_model.text())
                        del tb['select'], tb['index_field'], tb['where'], tb['text_date']
                        db['append_table'].append(tb)
                        selected_db = True
                if selected_db:
                    self._config['db'].append(db)
                if len(db) == 0:
                    return False
            return True

        self._config['db'] = []
        if self._data[self._tab_map[TabMap.MD5]]:
            gen_md5()
        if self._data[self._tab_map[TabMap.LOCAL]]:
            gen_local()
        if self._data[self._tab_map[TabMap.ALIYUN]]:
            gen_aliyun()
        if self._data[self._tab_map[TabMap.ORIGIN]]:
            gen_origin()
        elif self.filehandler.need_save:
            self._config['db'].append(self.filehandler.gen_conf())
        else:
            self._cond_set = True

        if len(self._config['db']):
            if not self._cond_set:
                ok = MessageBox().information(self, u'注意', u'没有选择增量条件,所有表都会全量同步.是否保存?',
                                              MessageBox.Yes | MessageBox.Cancel)
                if ok == MessageBox.Cancel:
                    return
            try:
                with open('conf/opends.conf', 'w') as profile:
                    json.dump(self._config, profile, indent=4, separators=(',', ':'))
                    MessageBox().information(self, u'提示', u'保存成功.')
                    self.pushButton_create_task.setDisabled(False)
                    self.pushButton_run.setDisabled(False)
            except Exception, e:
                MessageBox().critical(self, u'error', u'%s' % str(e))
        else:
            MessageBox().critical(self, u'提示', u'没有选择表。')

    def message_loop(self, m_type, m_content):
        """
            The main message loop, to dispatch message come from sub thread.
        Args:
            m_type: message's type, reference from util.app_global.g
            m_content: the message content
        """
        if m_type == g.SYNC_LOG:
            self.textBrowser_sync_log.append(m_content.replace('\n', ''))
        elif m_type == g.SYNC_STATE:
            if m_content == 'done':
                self.pushButton_run.setText(u'运行')
                self.pushButton_run.setDisabled(False)
                self.pushButton_db_connect_origin.setDisabled(False)
                self._syncing.sync_done()
        elif m_type == g.SYNC_TABLE_STATE:
            set_table_state(self._sync_tree_model, m_content)
        else:
            process_log().warning('Unhandled signal (%s)->(%s)' % (m_type, m_content))

    def run(self):
        self.textBrowser_sync_log.clear()
        self._syncing.sync_start()

        dbs = self._config.get('db')
        dbs = dbs if dbs else self._config_history.get('db')

        render_sync_tree(self._sync_tree_model, dbs)
        self.treeView_sync.expandAll()

        self.pushButton_run.setText(u'同步中')
        self.pushButton_run.setDisabled(True)
        self.pushButton_db_connect_origin.setDisabled(True)

        self.tabWidget_mainwindow.setCurrentWidget(self.tab_sync)

    def create_task(self):
        hour = self.timeEdit_origin.time().hour()
        hour = hour if len(str(hour)) == 2 else '0%s' % hour
        minute = self.timeEdit_origin.time().minute()
        minute = minute if len(str(minute)) == 2 else '0%s' % minute
        path = os.getcwd()
        win_release = ['7', '2012Server', '2008ServerR2', '2008Server', '8', '10']
        task_name = self._user_info.get('task_name', 'bdp_sync')

        # windows 创建计划任务
        if platform.system() == 'Windows':
            task_name, ok = QInputDialog().getText(self, u'提示', u'请输入任务名称(英文)', text=task_name)
            if ok:
                if platform.release() in win_release:
                    schtasks = "schtasks /create /tn %s /tr \"\\\"%s\\run.exe\\\"\" \
                                /sc daily /st %s:%s:00 /v1 & pause" \
                               % (task_name, path, hour, minute)
                else:
                    schtasks = "schtasks /create /tn %s /tr \"\\\"%s\\run.exe\\\"\" /sc daily /st %s:%s:00 & pause" \
                               % (task_name, path, hour, minute)
                process_log().info('Windows release: [%s], create task by command:%s' % (platform.release(), schtasks))
                os.system(schtasks)
                self._user_info['task_name'] = str(task_name)
                self.save()
        else:
            # unix 守护程序 or crontab
            pass

    def upgrade(self):
        if getattr(sys, "frozen", False):
            if self.check_upgrade():
                if self.best_version is not None:
                    self.progress.show()
                    self.progress.label_title.setText(u'更新至 %s' % self.best_version)
                    self.render_changelog(version, self.best_version)

                else:
                    MessageBox().information(self, u'提示', u'当前已经是最新版.')
        else:
            self.render_changelog('1.2.12', '9.9.9')
            self.progress.show()

    def render_changelog(self, current_version, best_version):
        if not best_version:
            return
        changelog = ClientService().changelog(current_version, best_version)
        self.progress.textBrowser.clear()
        if not changelog:
            self.progress.textBrowser.append(u'获取更新日志失败.')
            return
        for change in sorted(changelog, key=lambda x: x['version']):
            self.progress.textBrowser.append(changelog_template % change)

    def upgrading(self, args=None):
        if not args:
            args = {}
        status = args.get('status')
        if status == 'downloading':
            step = args.get('received') * 100 / args.get('size')
            info = '%s/%s' % (byte_format(args.get('received'), 'MB'), byte_format(args.get('size'), 'MB'))
            self.progress.label_value.setText(info)
            self.progress.progressBar.setValue(step)
        elif status == 'error':
            pass
        elif status == 'done':
            self.progress.progressBar.setValue(100)
            self.progress.label_tips.setText(u'完成')
            self.progress.label_value.setText(u'')
            self.progress.textBrowser.append(u'新版本%s安装成功.\n\r请重启软件。\n\r' % self.best_version)
            self.progress.pushButton.setText(u'确定')
            self.progress.pushButton.setDisabled(False)
        elif status == 'ready':
            info = u'下载完成,准备安装...'
            self.progress.textBrowser.append(info)
        elif status == 'installing':
            info = u'正在安装 %s...' % args.get('new_version')
            self.install_version = args.get('new_version')
            self.progress.textBrowser.append(info)
        elif status == 'cleaning up':
            info = u'清除安装包...'
            self.progress.textBrowser.append(info)
        else:
            pass
            # self.progress.textBrowser.append(u'step: %s..' % args)
        QApplication.processEvents()
        time.sleep(0.01)

    def progress_hook(self):
        sender = str(self.sender().text())
        if sender == u'更新':
            self.progress.pushButton.setText(u'更新中')
            self.progress.pushButton.setDisabled(True)
            self.app.auto_update(self.upgrading)
            QTimer().singleShot(0, self.upgrading)
        elif sender == u'确定':
            try:
                MessageBox().information(self, u'注意', u'!!!注意,如果创建了计划任务,重启后需要重新创建。')
                self.back_up_conf()
                # self._syncing.join()
                sys.exit(0)
            except Exception, e:
                raise e

    def back_up_conf(self):
        pwd = os.getcwd()
        if self._first_install:
            appdata = "%s/../appdata" % pwd
        else:
            appdata = "%s/../" % pwd

        appdata = os.path.abspath(appdata)
        for backup_dir in self._need_to_backup:
            src = os.path.abspath("%s/%s" % (pwd, backup_dir))
            dst = os.path.abspath("%s/%s" % (appdata, backup_dir))
            if not os.path.exists(src):
                process_log().info('Path `%s` does not exists, backup skipped.' % src)
            else:
                try:
                    process_log().info('Backup data from `%s` to `%s`' % (src, dst))
                    copy_and_overwrite(src, dst)
                except Exception, e:
                    raise e

    def check_conf_version(self):
        cur_conf = 'conf/opends.conf'
        backup_conf = '../conf/opends.conf'
        if not os.path.exists(cur_conf) and os.path.exists(backup_conf):
            with open(backup_conf, 'r') as f:
                conf = json.load(f)
                _version = conf.get('app', {}).get('version', -1)
            if _version < '110' and 'user_info' in conf and 'password' in conf['user_info']:
                pwd = conf['user_info']['password']
                try:
                    pwd = DataCrypter().decrypt_data(pwd)
                except Exception:
                    pass
                conf['user_info']['password'] = DataCrypter().encrypt_data(pwd)
                with open(backup_conf, 'w') as f:
                    json.dump(conf, f, indent=4, separators=(',', ':'))
            if _version >= conf_version_require:
                self.recover_conf()

    def recover_conf(self):
        pwd = os.getcwd()
        for backup_dir in self._need_to_backup:
            src = "%s/../%s" % (pwd, backup_dir)
            src = os.path.abspath(src)
            dst = "%s/%s" % (pwd, backup_dir)
            dst = os.path.abspath(dst)
            if os.path.exists(src):
                copy_and_overwrite(src, dst)

    def debug(self, text):
        MessageBox().information(self, 'debug', str(text))


def main():
    app = QApplication(sys.argv)
    exe = App()
    exe.setWindowTitle('bdp_sync')
    exe.show()
    sys.exit(app.exec_())


if __name__ == '__main__':
    main()
